package com.guli.mall.util;
/*
 * @Author 罗
 * @date 2020/10/13 - 7:48 下午
 */

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.guli.common.to.SpuEsTo;
import com.guli.common.utils.QuickSort;
import com.guli.common.utils.R;
import com.guli.mall.feign.ProductServiceFeignClient;
import com.guli.mall.service.impl.ProductSearchServiceImpl;
import com.guli.mall.vo.BrandVo;
import com.guli.mall.vo.SearchParamsVo;
import com.guli.mall.vo.SearchResultVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
public class EsResponseUtil {

    public static final TypeReference<SpuEsTo> TYPE_REFERENCE_SPU_ES_TO = new TypeReference<SpuEsTo>() {
    };

    /**
     * @param searchResponse
     * @param size                      每页记录数
     * @param totalSpuCount             总的 spu 个数
     * @param searchParamsVo            查询的参数
     * @param productServiceFeignClient
     * @return
     */
    public static SearchResultVo parseEsResponseToSpuEsTo(SearchResponse searchResponse, Integer size, Integer totalSpuCount, SearchParamsVo searchParamsVo, ProductServiceFeignClient productServiceFeignClient) {

        Aggregations aggregations = searchResponse.getAggregations();

        // 1、获取 AttrAgg 聚合（注意是嵌套类型 nested，一级聚合，一级聚合只做扁平化处理）
        Nested attrAgg = aggregations.get(ProductSearchServiceImpl.ATTR_AGG_NAME);

        Aggregations subAttrAgg = attrAgg.getAggregations();

        // 获取 attrIdAgg 聚合 （二级聚合，AttrAgg的子聚合，真正的内容放在二级聚合及其以上）
        Terms attrIdAgg = subAttrAgg.get(ProductSearchServiceImpl.ATTR_ID_AGG_NAME);

        List<? extends Terms.Bucket> buckets = attrIdAgg.getBuckets();

        List<SearchResultVo.Attribution> attrList = buckets.stream().map(attrIdBucket -> {

            /**
             * 用于封装 attr 聚合
             * */
            SearchResultVo.Attribution attribution = new SearchResultVo.Attribution();

            // 解析并设置 attrId
            Long attrId = Long.parseLong(attrIdBucket.getKey().toString());

            attribution.setAttrId(attrId);

            Aggregations subAttrIdAgg = attrIdBucket.getAggregations();

            // 获取 attrNameAgg 聚合（三级聚合，AttrIdAgg的子聚合）
            Terms attrNameAgg = subAttrIdAgg.get(ProductSearchServiceImpl.ATTR_NAME_AGG_NAME);

            List<? extends Terms.Bucket> attrNameAggBuckets = attrNameAgg.getBuckets();

            attrNameAggBuckets.forEach(attrNameBucket -> {

                // 设置 attrName 到 attribution 中
                attribution.setAttrName(attrNameBucket.getKeyAsString());

                Aggregations subAttrNameAgg = attrNameBucket.getAggregations();

                // 获取 attrNameValueAgg 聚合（四级聚合，attrNameAgg的子聚合）
                Terms attrNameValueAgg = subAttrNameAgg.get(ProductSearchServiceImpl.ATTR_NAME_VALUE_AGG_NAME);

                attrNameValueAgg.getBuckets().forEach(attrNameValueBucket -> {
                    // 将 attrNameValue 添加到 attribution 的 attrValues 集合中
                    String nameValue = attrNameValueBucket.getKeyAsString();

                    // 如果包含逗号(,) 就按照逗号(,)分割后再添加
                    if (nameValue.contains(",")) {
                        String[] split = nameValue.split(",");
                        for (int i = 0; i < split.length; i++) {
                            attribution.getAttrValues().add(split[i]);

                        }
                    } else {

                        attribution.getAttrValues().add(nameValue);
                    }

                });
            });

            return attribution;
        }).collect(Collectors.toList());

        // 2、获取 catelogIdAgg 聚合（一级聚合），并封装
        Terms catelogIdAgg = aggregations.get(ProductSearchServiceImpl.CATELOG_ID_AGG_NAME);

        List<SearchResultVo.Category> categoryList = catelogIdAgg.getBuckets().stream().map(catelogIdBucket -> {

            // 创建 vo 用于封装 catelogId 和 catelogName
            SearchResultVo.Category category = new SearchResultVo.Category();

            category.setCategoryId(Integer.parseInt(catelogIdBucket.getKeyAsString()));

            Aggregations subCategoryIdAgg = catelogIdBucket.getAggregations();

            Terms categoryLogNameAgg = subCategoryIdAgg.get(ProductSearchServiceImpl.CATELOG_NAME_AGG_NAME);

            // 设置 该 category 下的 categoryName（正常情况下一个 categoryId 只对应一个 categoryName，所以这样设置）
            categoryLogNameAgg.getBuckets().forEach(categoryNameBucket -> {
                category.setCategoryName(categoryNameBucket.getKeyAsString());
            });

            return category;
        }).collect(Collectors.toList());

        List<Long> categoryIdList = categoryList.stream().map(category -> {
            return category.getCategoryId().longValue();
        }).collect(Collectors.toList());


        // 3、获取 brandIdAgg 聚合 并封装（不应该从聚合中封装品牌，而是应该调用品牌微服务来查询该目录下的对应品牌）
        //List<Long> categoryIdListForSearchBrand = CollectionUtils.isEmpty(categoryIdList) ? Arrays.asList(searchParamsVo.getCatalog3Id().longValue()) : categoryIdList;

        R r = productServiceFeignClient.getBrandListByCategoryIdList(categoryIdList);

        List<BrandVo> brandVoList = r.getData(new TypeReference<List<BrandVo>>() {
        });

        List<SearchResultVo.Brand> brands = brandVoList.stream().map(brandVo -> {

            SearchResultVo.Brand brand = new SearchResultVo.Brand();

            brand.setBrandImg(brandVo.getLogo());

            brand.setBrandName(brandVo.getName());

            brand.setBrandId(brandVo.getBrandId());

            return brand;
        }).collect(Collectors.toList());

        /*        Terms brandIdAgg = aggregations.get(ProductSearchServiceImpl.BRAND_ID_AGG_NAME);

        List<SearchResultVo.Brand> brands = brandIdAgg.getBuckets().stream().map(brandIdBucket -> {

            SearchResultVo.Brand brand = new SearchResultVo.Brand();

            // 解析并设置 brandId
            long brandId = Long.parseLong(brandIdBucket.getKeyAsString());

            brand.setBrandId(brandId);

            Aggregations subBrandIdAgg = brandIdBucket.getAggregations();

            // 获取子聚合 brandNameAgg 并设置brandName
            Terms brandNameAgg = subBrandIdAgg.get(ProductSearchServiceImpl.BRAND_NAME_AGG_NAME);

            brandNameAgg.getBuckets().forEach(brandNameBucket -> {
                brand.setBrandName(brandNameBucket.getKeyAsString());
            });

            // 获取子聚合 brandImg 并设置BrandImg
            Terms brandImgAgg = subBrandIdAgg.get(ProductSearchServiceImpl.BRAND_IMG_AGG_NAME);
            brandImgAgg.getBuckets().forEach(brandImgBucket -> {
                brand.setBrandImg(brandImgBucket.getKeyAsString());
            });

            return brand;
        }).collect(Collectors.toList());*/

        // 4、解析查询到的记录
        SearchHit[] hits = searchResponse.getHits().getHits();

        Map<Long, SearchResultVo.Hit> hitMap = new HashMap<>();


        Arrays.stream(hits).map(hit -> {

            Map<String, HighlightField> highlightFields = hit.getHighlightFields();

            HighlightField highlightField = highlightFields.get("skuTitle");

            String sourceString = hit.getSourceAsString();
            // 将 elastic search 响应来的 hits 字符串解析为 SpuEsTo 对象
            SpuEsTo spuEsTo = JSON.parseObject(sourceString, TYPE_REFERENCE_SPU_ES_TO);

            if (!ObjectUtils.isEmpty(highlightField)) {
                /*
                因为高亮的属性只是数组中的第0个元素，没必要用 StringBuilder 来拼接唯一的一个字符串
                StringBuilder highLightBuilder = new StringBuilder();
                Arrays.stream(highlightField.getFragments()).forEach(text -> {
                    highLightBuilder.append(text.toString());
                });
                spuEsTo.setSkuTitle(highLightBuilder.toString());*/

                String highLightFieldStr = highlightField.getFragments()[0].toString();
                spuEsTo.setSkuTitle(highLightFieldStr);
            }
            return spuEsTo;

        }).forEach(spuEsTo -> {

            Long spuId = spuEsTo.getSpuId();

            SearchResultVo.Hit hit = hitMap.get(spuId);

            if (ObjectUtils.isEmpty(hit)) {

                hit = new SearchResultVo.Hit();
                hit.setSpuId(spuId);
                hit.setSkuList(new ArrayList<>());
                hitMap.put(spuId, hit);

            }

            // 将 spuEsTo 根据 spuId 分类，spuId 作为 key 放入 HashMap，而spuId相同的 spuEsTo 就放入到 HashMap 的同一个list中，这个list作为 hashMap 的value

            hit.getSkuList().add(spuEsTo);

            // 为每一个spu统计sku的销量总和，（避免空指针）
            long singleSkuSaleCount = ObjectUtils.isEmpty(spuEsTo.getSaleCount()) ? 0 : spuEsTo.getSaleCount();
            hit.setSaleCount(hit.getSaleCount() + (int) singleSkuSaleCount);

            // 为每一个spu统计sku的价格总和，（避免空指针）
            Integer singleSkuPrice = ObjectUtils.isEmpty(spuEsTo.getSkuPrice()) ? 0 : spuEsTo.getSkuPrice().intValue();
            hit.setSpuAvgPrice(hit.getSpuAvgPrice() + singleSkuPrice);
        });

        SearchResultVo searchResultVo = new SearchResultVo();

        searchResultVo.setAttrs(attrList);

        searchResultVo.setCategories(categoryList);

        searchResultVo.setBrands(brands);

        /* 二者等效，但不是真实的数据
        获取总共查询到的记录条数（这是过滤之后用到的数据，不准确）
        //TotalHits totalHits = searchResponse.getHits().getTotalHits();

        // 但是我使用查询到的总 spu 数，作为总记录数
        //Integer totalItemsNumber = hitMap.size();
        */
        // 每页记录数
        int pageSize = ObjectUtils.isEmpty(size) ? ProductSearchServiceImpl.SEARCH_DEFAULT_PAGE_SIZE : size;

        searchResultVo.setTotal(totalSpuCount.longValue());

        // 总页数
        Integer totalPageNum = totalSpuCount % pageSize == 0 ? totalSpuCount / pageSize : totalSpuCount / pageSize + 1;

        searchResultVo.setTotalPageNum(totalPageNum);
        //searchResultVo.setTotalPageNum(100);

        //searchResultVo.setTotal(totalHits.value);

        // 将 HashMap<Long,List<SpuEsTo>> 中的 List<SpuEsTo>提取出来放入到 searchResultVo 的hits中
        List<SearchResultVo.Hit> hitList = new ArrayList<>(hitMap.values());

        /**
         * 手动进行排序
         * */
        if (!ObjectUtils.isEmpty(searchParamsVo.getSortCondition())) {

            String[] s = searchParamsVo.getSortCondition().split("_");

            if (s.length == 2) {
                Comparator<SearchResultVo.Hit> comparator = null;
                // 按照销量进行排序
                if ("saleCount".equals(s[0])) {
                    if ("asc".equals(s[1])) {
                        comparator = (c1, c2) -> {
                            return c1.getSaleCount() - c2.getSaleCount();
                        };
                    } else {
                        comparator = (c1, c2) -> {
                            return c2.getSaleCount() - c1.getSaleCount();
                        };
                    }
                }
                // 按照spu的平均价格进行排序
                if ("price".equals(s[0])) {

                    // 计算平均价格
                    hitMap.forEach((key, hit) -> {
                        /**
                         * 因为之前 hit.getSpuAvgPrice() 中存放的是该spu下所有sku的总价格
                         * sku的总价格要除以 sku的个数才是平均价格
                         * */
                        int avgPrice = hit.getSpuAvgPrice() / hit.getSkuList().size();
                        hit.setSpuAvgPrice(avgPrice);
                    });

                    if ("asc".equals(s[1])) {
                        comparator = (c1, c2) -> {
                            return c1.getSpuAvgPrice() - c2.getSpuAvgPrice();
                        };
                    } else {
                        comparator = (c1, c2) -> {
                            return c2.getSpuAvgPrice() - c1.getSpuAvgPrice();
                        };
                    }
                }

                if (!ObjectUtils.isEmpty(comparator)) {

                    QuickSort.sort(hitList, comparator);
                }
            }

        }


        searchResultVo.setHits(hitList);

        return searchResultVo;

    }
}
